#ifndef _TOKENSITERATOR_H_
#define _TOKENSITERATOR_H_
#include "Token.h"
#include "TokenManager.h"
#include "CharStream.h"
#include "python3/PythonGrammar30Constants.h"

struct TupleObj
{
    TokenPtr o1;
    TokenPtr o2;
};

class TokensIterator
{
private:
    TokenPtr _currentToken;
    int _tokensToIterate;
    int _tokensIterated;
    bool _breakOnIndentsDedentsAndNewCompounds;
    TokenManager* _tokenManager;
    TupleObj _prevAndReturned;
    QList<int> _contextsToBreak;
    bool _calculatedNext;
    bool _isFirst;
public:
    TokensIterator(TokenManager* tokenManager, const TokenPtr& firstIterationToken, int tokensToIterate, bool breakOnIndentsDedentsAndNewCompounds)
    {
        _contextsToBreak.append(INDENT);
        _contextsToBreak.append(DEDENT);
        
        _contextsToBreak.append(IF);
        _contextsToBreak.append(WHILE);
        _contextsToBreak.append(FOR);
        _contextsToBreak.append(TRY);
        
        _contextsToBreak.append(DEF);
        _contextsToBreak.append(CLASS);
        _contextsToBreak.append(AT);
        
        reset(tokenManager, firstIterationToken, tokensToIterate, breakOnIndentsDedentsAndNewCompounds);
    }
    
    
    void reset(TokenManager* tokenManager, const TokenPtr& firstIterationToken, int tokensToIterate, bool breakOnIndentsDedentsAndNewCompounds)
    {
        _currentToken = firstIterationToken;
        _tokenManager = tokenManager;
        _tokensToIterate = tokensToIterate;
        _breakOnIndentsDedentsAndNewCompounds = breakOnIndentsDedentsAndNewCompounds;
        _tokensIterated = 0;
        _tokensToIterate = 0;
        _prevAndReturned.o1.clear();
        _prevAndReturned.o2.clear();
        _calculatedNext = false;
        _isFirst = true;
    }

    
    bool hasNext()
    {
        if (_isFirst) {
            return _currentToken != NULL;
        }
        if (!_calculatedNext) {
            calculateNext();
            _calculatedNext = true;
        }
        return _currentToken != NULL && _currentToken->next != NULL;
    }

    TokenPtr next()
    {
        if (_isFirst) {
            _isFirst = false;
            return _currentToken;
        }
        if (!_calculatedNext) {
            calculateNext();
        }
        
        _calculatedNext = false;
        _tokensIterated += 1;
        if(!_currentToken || !_currentToken->next){
            return TokenPtr(NULL);
        }
        _prevAndReturned.o1 = _prevAndReturned.o2;
        _prevAndReturned.o2 = _currentToken->next;
        
        if (_tokensIterated == _tokensToIterate){
            _currentToken =  TokenPtr(NULL);
        } else {
            if (_currentToken && _currentToken->kind == _EOF) {
                //always break on EOF
                _currentToken =  TokenPtr(NULL);
            } else if(_breakOnIndentsDedentsAndNewCompounds) {
                if(_currentToken && _contextsToBreak.contains(_currentToken->kind)){
                    _currentToken =  TokenPtr(NULL); // we must break it now (indent or dedent found)
                }
            }
        }
        if(_currentToken){
            _currentToken = _currentToken->next;
        }
        return _prevAndReturned.o2;
    }

    void calculateNext()
    {
        if(!_currentToken){
            return;
        }
        
        if(_currentToken->kind == _EOF){
            //found end of file!
            _currentToken =  TokenPtr(NULL);
            return;
        }
        
        
        if(_currentToken->next == NULL){
            _currentToken->next = nextTokenConsideringNewLine(_tokenManager);
        }
    }

    void remove()
    {
    }


    TokenPtr getBeforeLastReturned()
    {
        return _prevAndReturned.o1;
    }
private:
    TokenPtr nextTokenConsideringNewLine(TokenManager* tokenManager)
    {
        bool foundNewLine = searchNewLine(true);
        if(foundNewLine){
            tokenManager->indenting(0);
        }
        TokenPtr nextToken = tokenManager->getNextToken();
        return nextToken;
    }

    bool searchNewLine(bool breakOnFirstNotWhitespace)
    {
        bool foundNewLine = false;
        CharStream* inputStream = _tokenManager->getInputStream();
        int currentPos = inputStream->getCurrentPos();

        while(!inputStream->endOfInput()){
            char c = inputStream->readChar();
            if(c == '\r' || c == '\n'){
                if(c == '\r'){
                    c = inputStream->readChar();
                    if(c != '\n'){
                        inputStream->backup(1);
                    }
                }
                foundNewLine = true;
                break;
            }
            if(breakOnFirstNotWhitespace && !QChar(c).isSpace()){
                break;
            }
        }
        if(!foundNewLine){
            inputStream->restorePos(currentPos);
        }
        return foundNewLine;
    }
};

#endif
